/*
 * Attribute.java
 *
 * Created on 20 marzo 2004, 10.41
 */

package net.ermannofranco.xml.schema;

import net.ermannofranco.xml.Static;

/**
 * formalizzazione del tag attribute
 * 
 * @author ermanno (ermanno.franco@gmail.com)
 */
public class Attribute extends SchemaTag {

	private static final long serialVersionUID = -5303622557139666675L;
	// private String nome;
	// private String type;
	// private String ref;
	// private boolean simple;
	private boolean named;
	private boolean typeAssigned;
	private boolean refAssigned;
	private boolean defFix;
	private boolean defaulted;
	private boolean useReq;
	private String nome;

	/**
	 * Creates a new instance of Attribute Protetto
	 * 
	 * @param level
	 *            livello di indentazione
	 * @param container
	 *            tag contenitore
	 */

	protected Attribute(int level, ISchemaTag container) {
		super("attribute", level, container);
	}

	/**
	 * Shorcut for setName(name).setType(type);
	 * 
	 * @param name
	 * @param type
	 *            simple xml type enum
	 * @return
	 */
	protected Attribute setNameAndType(String name, XmlType type) {
		setName(name).setType(type);
		return this;
	}

	/**
	 * Shorcut for setName(name).setType(type);
	 * 
	 * @param name
	 * @param type
	 *            reference to a declared simple or complex type
	 * @return
	 */
	protected Attribute setNameAndType(String name, ContentType type) {
		setName(name).setType(type);
		return this;
	}

	/**
	 * Shorcut for setName(name).setType(type);
	 * 
	 * @param name
	 * @param typeName
	 *            type name as declared in document.
	 * @return
	 */
	protected Attribute setNameAndType(String name, String typeName) {
		setName(name).setType(typeName);
		return this;
	}

	/**
	 * imposta il nome dell'attributo
	 * 
	 * @param name
	 *            il nome dell'attributo
	 * @return autoref
	 */
	public Attribute setName(String name) {
		if (named)
			throw new SchemaException("nome o ref gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		Static.validateNMToken(name);// il nome va controllato ecc........
		nome = name;
		addAttr("name", nome);
		named = true;
		return this;
	}

	// override per restituire il nome dell'elemento
	// altrimenti restituirebbe 'attribute'
	/**
	 * restituisce il nome dell'attributo
	 * 
	 * @return il nome dell'attributo
	 */
	public String getName() {
		return nome;
	}

	/**
	 * imposta il riferimento dell'attributo ad un'altra definizione
	 * 
	 * @param ref
	 *            nome del ref
	 * @return autoref
	 */
	public Attribute setRef(String ref) {
		if (named)
			throw new SchemaException("nome o ref gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		if (typeAssigned)
			throw new SchemaException("tipo gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		Static.validateNMToken(ref);
		// removeAttribute("type");//attenz agli attr tolti...
		addAttr("ref", feedFormPrefix(ref));
		named = true;
		refAssigned = true;
		return this;
	}

	public Attribute setRef(Attribute ref) {
		return setRef(ref.getName());
	}

	/**
	 * imposta l'uso, opzionale, richiesto o proibito
	 * 
	 * @param useType
	 *            costante statica di Use
	 * @return autoref
	 */
	public Attribute setUse(Use useType) {
		if (getLevel() == 1)
			throw new SchemaException("Use not allowed at first level");
		if (defaulted && useType == Use.REQUIRED)
			throw new SchemaException(
					"default e use_required sono mutuamente esclusivi, "
							+ getClass().getName() + ":(" + getName() + ")");
		addAttr("use", Use.getString(useType));
		if (useType == Use.REQUIRED)
			useReq = true;
		return this;
	}

	/**
	 * imposta il tipo
	 * 
	 * @param simpleTypeNumber
	 *            tipo di base, enum XmlType
	 * @return autoref
	 */
	protected Attribute setType(XmlType simpleTypeNumber) {
		if (refAssigned)
			throw new SchemaException("tipo non ammesso, ref gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		if (typeAssigned)
			throw new SchemaException("tipo gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		addAttr("type", getLocalNS() + ":" + XmlType.getType(simpleTypeNumber));
		typeAssigned = true;
		return this;
	}

	/**
	 * Error-safe method, assignment by reference, not by name...
	 * 
	 * @param typeRef
	 *            a declared type (simple or complex).
	 * @return this.
	 */
	protected Attribute setType(ContentType typeRef) {
		setType(typeRef.getTypeName());
		return this;
	}

	/**
	 * imposta il tipo
	 * 
	 * @param typeName
	 *            nome del tipo personalizzato
	 * @return autoref
	 */
	protected Attribute setType(String typeName) {
		if (refAssigned)
			throw new SchemaException("tipo non ammesso, ref gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		if (typeAssigned)
			throw new SchemaException("tipo gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		Static.validateNMToken(typeName);
		addAttr("type", feedFormPrefix(typeName));
		typeAssigned = true;
		return this;
	}

	/**
	 * dichiara che l'attributo e' di un tipo semplice anonimo
	 * 
	 * @return ref al tipo semplice generato
	 */
	public SimpleType setSimpleType() { // da fare, come per element
		if (refAssigned)
			throw new SchemaException("tipo non ammesso, ref gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		if (typeAssigned)
			throw new SchemaException("contenuto gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		SimpleType att = new SimpleType(getLevel() + 1, this);
		add(att);// vado ad aggiungerlo all'ArrayList della superclasse Tag
		typeAssigned = true;
		return att;
	}

	/**
	 * Shortcut for setDefault("0").
	 * 
	 * @return
	 */
	public Attribute setDefaultZero() {
		return setDefault("0");
	}

	/**
	 * Shortcut for setDefault("1").
	 * 
	 * @return
	 */
	public Attribute setDefaultOne() {
		return setDefault("1");
	}

	/**
	 * Shortcut for setDefault(Static.TRUE).
	 * 
	 * @return
	 */
	public Attribute setDefaultTrue() {
		return setDefault(Static.TRUE);
	}

	/**
	 * Shortcut for setDefault(Static.FALSE).
	 * 
	 * @return
	 */
	public Attribute setDefaultFalse() {
		return setDefault(Static.FALSE);
	}

	/**
	 * imposta un valore di default
	 * 
	 * @param dflt
	 *            valore di default
	 * @return autoref
	 */
	public Attribute setDefault(String dflt) {
		if (useReq)
			throw new SchemaException(
					"default non ammesso, use_required gia' assegnato, "
							+ getClass().getName() + ":(" + getName() + ")");
		if (defFix)
			throw new SchemaException(
					"default non ammesso, fix o default gia' assegnato, "
							+ getClass().getName() + ":(" + getName() + ")");
		addAttr("default", dflt);
		defFix = true;
		defaulted = true;
		return this;
	}

	/**
	 * fissa il valore dell'attributo
	 * 
	 * @param fixed
	 *            valore dell'attributo
	 * @return autoref
	 */
	public Attribute fix(String fixed) {
		if (defFix)
			throw new SchemaException(
					"default non ammesso, fix o default gia' assegnato, "
							+ getClass().getName() + ":(" + getName() + ")");
		addAttr("fixed", fixed);
		defFix = true;
		return this;
	}

	/**
	 * imposta la qualifica di form
	 * 
	 * @param formValue
	 *            Costante di FormValues
	 * @return autoref
	 */
	public Attribute setForm(FormValues formValue) {
		addAttr("form", FormValues.getValue(formValue));
		return this;
	}

	/**
	 * aggiunge un tag Annotation che contiene un tag Documentation
	 * 
	 * @param explanation
	 *            Stringa di documentazione
	 * @return autoriferimento all'oggetto corrente
	 */
	public Documentation addDocumentation(String explanation) {
		return addAnnotation().addDocumentation(explanation);
	}

	/**
	 * aggiunge un tag Annotation che contiene un tag Documentation
	 * 
	 * @param explanation
	 *            testo della documentazione
	 * @param language
	 *            attributo di xml:lang, seguire le regole di i18n
	 * @return riferimento all'oggetto corrente
	 */
	public Documentation addDocumentation(String explanation, String language) {
		return addAnnotation().addDocumentation(explanation, language);
	}

	public RestrictionSimpleType setRestriction(XmlType simpleTypeNumber) {
		if (refAssigned)
			throw new SchemaException("tipo non ammesso, ref gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");
		if (typeAssigned)
			throw new SchemaException("contenuto gia' assegnato, "
					+ getClass().getName() + ":(" + getName() + ")");

		RestrictionSimpleType re;
		add(re = new RestrictionSimpleType(getLevel() + 1, this));
		re.setBase(simpleTypeNumber);
		typeAssigned = true;
		return re;
	}

	/**
	 * assegna una restrizione al tipo; metodo che semplifica l'operazione in
	 * caso di restrizione semplice
	 * 
	 * @param simpleTypeNumber
	 *            tipo interessato dalla restrizione
	 * @param facetType
	 *            indicatore di restrizione. Usare costante della classe Facet
	 * @param value
	 *            valore della restrizione
	 * @return autoref, a Attribute, NON a Restriction
	 */
	public Attribute setRestriction(XmlType simpleTypeNumber, Facet facetType,
			String value) {
		RestrictionSimpleType re = setRestriction(simpleTypeNumber);
		re.addFacet(facetType, value);
		return this;
	}

	private void validateAttr() {
		if (refAssigned && getLevel() == 1)
			throw new SchemaException("ref non ammesso al primo livello, "
					+ getClass().getName() + ":(" + getName() + ")");
		if (!named && getLevel() == 1)
			throw new SchemaException("nome necessario al primo livello, "
					+ getClass().getName() + ":(" + getName() + ")");
		// if (!refAssigned && !typeAssigned)
		// throw new SchemaException(
		// "specificare il tipo di elemento o assegnare un riferimento, "
		// + getClass().getName() + ":(" + getName() + ")");
		if (!(named || refAssigned))
			throw new SchemaException("specificare uno tra name e ref, "
					+ getClass().getName() + ":(" + getName() + ")");
	}

	/**
	 * semplice override di convalida
	 * 
	 * @return
	 */
	public String toPrint() {
		validateAttr();
		return super.toPrint();
	}

	public String toSave() {
		validateAttr();
		return super.toSave();
	}

}
